import { privacyInput } from './input'
import { formatFileSize } from '@/utils/format'
import type ora from 'ora'
import type { PrivacyInputOptions } from './input'

export class FileSpinner {
  constructor(spinner: ora.Ora, options: FileSpinnerOptions = {}) {
    const {
      updateInterval = 500,
      startText,
      prefix = '',
      showCount = false,
      showSpeed = false,
      averageSpeed = false
    } = options

    this.spinner = spinner
    this.updateInterval = updateInterval
    this.prefix = prefix
    this.showCount = showCount
    this.showSpeed = showSpeed
    this.averageSpeed = averageSpeed

    if (startText) spinner.text = startText
  }

  readonly prefix: string
  readonly showCount: boolean
  readonly showSpeed: boolean
  readonly averageSpeed: boolean
  readonly spinner: ora.Ora
  readonly updateInterval: number
  readonly startTime = Date.now()

  totalDataVolume = 0
  totalDuration = 0
  speed = 0
  protected lastSpeed = ''
  protected lastUpdateTime = Date.now()

  protected showSize = false
  protected lastRelative = ''

  protected relative = ''
  protected totalSize = 0
  protected currentSize = 0
  protected retryCount = 0
  protected totalCount = 0
  protected currentCount = 0

  protected updateTimer?: NodeJS.Timeout

  update(data: FileSpinnerUpdateData) {
    this.relative = data.relative
    this.totalSize = data.totalSize
    this.currentSize = data.currentSize
    this.totalCount = data.totalCount || 0
    this.currentCount = data.currentCount || 0
    this.retryCount = data.retryCount || 0
    this.totalDataVolume += data.chunkSize || 0

    const now = Date.now()
    if (this.averageSpeed) {
      this.speed = (this.totalDataVolume / (now - this.startTime)) * 1000
    } else {
      // 秒速度
      this.speed = (data.chunkSize || 0 / (now - this.lastUpdateTime)) * 1000
    }

    if (!Number.isNaN(this.speed)) {
      this.lastSpeed = formatFileSize(this.speed) + '/s'
    }

    this.lastUpdateTime = now

    if (this.relative !== this.lastRelative) {
      this.lastRelative = this.relative
      clearTimeout(this.updateTimer)
      this.showSize = false
      this.updateTimer = setTimeout(() => {
        this.showSize = true
        this.render()
      }, this.updateInterval)
    }

    this.render()
  }

  protected render() {
    let text = ''

    if (this.prefix) {
      text += this.prefix + ' '
    }

    if (this.showSpeed) {
      text += this.lastSpeed.padEnd(10, ' ') + ' '
    }

    if (this.showCount) {
      text += `${this.totalCount}/${this.currentCount} `
    }

    text += this.relative

    if (this.showSize) {
      text += ` ${formatFileSize(this.totalSize)}/${formatFileSize(this.currentSize)}`
    }

    if (this.retryCount > 0) {
      text += ' ' + this.retryCount
    }

    this.spinner.text = text.trim()
    if (!this.spinner.isSpinning) {
      this.spinner.start()
    }
  }

  end() {
    clearTimeout(this.updateTimer)
    this.totalDuration = Date.now() - this.startTime
  }
}

type FileSpinnerOptions = {
  prefix?: string
  updateInterval?: number
  startText?: string
  showCount?: boolean
  showSpeed?: boolean
  averageSpeed?: boolean
}

type FileSpinnerUpdateData = {
  relative: string
  totalSize: number
  currentSize: number
  retryCount?: number
  totalCount?: number
  currentCount?: number
  chunkSize?: number
}

export const passwordInputSpinner = (
  spinner: ora.Ora,
  options: passwordInputSpinnerOptions = {}
) => {
  const { restart = true } = options

  spinner.stop()

  const { result, destroy } = privacyInput(options)

  return {
    result: result.then((password) => {
      if (restart) spinner.start()
      return password
    }),
    destroy
  }
}

export interface passwordInputSpinnerOptions extends PrivacyInputOptions {
  restart?: boolean
}
